package com.thx.common.orm;

import com.thx.common.util.ReflectUtils;
import com.thx.common.web.PropertyFilter;
import com.thx.common.web.PropertyFilter.MatchType;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 提供 Hibernate 基础方法.
 * 
 * @author 贾红磊
 */
@Repository
public class HibernateBaseDao<T> {
  protected Logger logger = LoggerFactory.getLogger(getClass());

  protected Class<T> entityClass;
  protected SessionFactory sessionFactory;

  public HibernateBaseDao() {
    entityClass = ReflectUtils.getClassGenricType(getClass());
  }

  /**
   * 取得sessionFactory.
   */
  public SessionFactory getSessionFactory() {
    return sessionFactory;
  }

  @Autowired
  public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
  }

  /**
   * 取得当前Session.
   */
  public Session getSession() {
    return sessionFactory.getCurrentSession();
  }

  /**
   * 保存或者更新.
   */
  public void saveOrUpdate(final Object entity) {
    getSession().saveOrUpdate(entity);
  }

  /**
   * 保存.
   */
  public void save(final Object entity) {
    getSession().save(entity);
  }

  /**
   * 更新.
   */
  public void update(final Object entity) {
    getSession().update(entity);
  }

  /**
   * 删除对象.
   */
  public void delete(final Object entity) {
    getSession().delete(entity);
  }

  /**
   * 根据ID删除对象.
   */
  public void delete(final Serializable id) {
    delete(get(id));
  }

  /**
   * 根据id获得对象.
   */
  public T get(Serializable id) {
    if (id != null) {
      T obj = (T) getSession().get(entityClass, id);
      return obj;
    } else {
      return null;
    }
  }

  /**
   * 获取所有数据.
   */
  public List<T> getAll() {
    return query();
  }

  /**
   * 根据条件获取数据.
   * 
   * @param criterions 数量可变的Criterion
   */
  public List<T> query(final Criterion... criterions) {
    return createCriteria(criterions).list();
  }

  /**
   * HQL方式查询.
   * 
   * @param hql 符合HQL语法的查询语句
   * @param values 数量可变的条件值,按顺序绑定
   */
  public Query createQuery(final String hql, final Object... values) {
    Query query = getSession().createQuery(hql);
    int len = values.length;
    for (int i = 0; i < len; i++) {
      query.setParameter(i, values[i]);
    }
    return query;
  }

  /**
   * SQL方式查询.
   * 
   * @param sql 符合SQL语法的查询语句
   * @param values 数量可变的条件值,按顺序绑定
   */
  public SQLQuery createSqlQuery(final String sql, final Object... values) {
    SQLQuery query = getSession().createSQLQuery(sql);
    if (values != null) {
      for (int i = 0; i < values.length; i++) {
        query.setParameter(i, values[i]);
      }
    }
    return query;
  }

  /**
   * 根据类型创建查询对象.
   * 
   * @param clazz :类型
   * 
   */
  public Criteria createCriteria(final Class clazz) {
    return getSession().createCriteria(clazz);
  }

  /**
   * 对象化查询.
   * 
   * @param clazz 类
   * @param criterions 数量可变的Criterion
   */
  public Criteria createCriteria(final Class clazz, final Criterion... criterions) {
    Criteria criteria = getSession().createCriteria(clazz);
    for (Criterion c : criterions) {
      criteria.add(c);
    }
    return criteria;
  }

  /**
   * 对象化查询.
   * 
   * @param criterions :数量可变的Criterion
   * 
   */
  public Criteria createCriteria(final Criterion... criterions) {
    return createCriteria(entityClass, criterions);
  }

  // -- 属性过滤条件(PropertyFilter)查询函数 --//

  /**
   * 按属性查找对象列表,支持多种匹配方式.
   * 
   * @param matchType 匹配方式,目前支持的取值见PropertyFilter的MatcheType enum.
   */
  public List<T> findBy(final String propertyName, final Object value, final MatchType matchType) {
    Criterion criterion = buildCriterion(propertyName, value, matchType);
    return query(criterion);
  }

  /**
   * 按属性过滤条件列表查找对象列表.
   */
  public List<T> find(List<PropertyFilter> filters) {
    Criterion[] criterions = buildCriterionByPropertyFilter(filters);
    return query(criterions);
  }

  /**
   * 按属性条件列表创建Criterion数组,辅助函数.
   */
  protected Criterion[] buildCriterionByPropertyFilter(final List<PropertyFilter> filters) {
    List<Criterion> criterionList = new ArrayList<Criterion>();
    for (PropertyFilter filter : filters) {
      if (!filter.hasMultiProperties()) { // 只有一个属性需要比较的情况.
        Criterion criterion =
            buildCriterion(filter.getPropertyName(), filter.getMatchValue(), filter.getMatchType());
        criterionList.add(criterion);
      } else {
        // 包含多个属性需要比较的情况,进行or处理.
        Disjunction disjunction = Restrictions.disjunction();
        for (String param : filter.getPropertyNames()) {
          Criterion criterion =
              buildCriterion(param, filter.getMatchValue(), filter.getMatchType());
          disjunction.add(criterion);
        }
        criterionList.add(disjunction);
      }
    }
    return criterionList.toArray(new Criterion[criterionList.size()]);
  }

  /**
   * 按属性条件参数创建Criterion,辅助函数.
   */
  protected Criterion buildCriterion(final String propertyName, final Object propertyValue,
      final MatchType matchType) {
    Assert.hasText(propertyName, "propertyName不能为空");
    Criterion criterion = null;
    // 根据MatchType构造criterion
    switch (matchType) {
      case EQ:
        criterion = Restrictions.eq(propertyName, propertyValue);
        break;
      case LIKE:
        criterion = Restrictions.like(propertyName, (String) propertyValue, MatchMode.ANYWHERE);
        break;
      case LE:
        criterion = Restrictions.le(propertyName, propertyValue);
        break;
      case LT:
        criterion = Restrictions.lt(propertyName, propertyValue);
        break;
      case GE:
        criterion = Restrictions.ge(propertyName, propertyValue);
        break;
      case GT:
        criterion = Restrictions.gt(propertyName, propertyValue);
        break;
      default:
        criterion = Restrictions.eq(propertyName, propertyValue);
        break;
    }
    return criterion;
  }
}
